package org.xx.armory.db.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.commons.AbstractLifeCycle;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.xx.armory.commons.Validators.notBlank;
import static org.xx.armory.commons.Validators.notNull;

/**
 * 用于测试的数据源，实现了数据源的基本功能。
 * <p>此数据源内部实现了简单的连接池，池的最大大小是固定的。默认最大3个连接。</p>
 *
 * @author Dev
 */
public final class SimpleDataSource
        extends AbstractLifeCycle
        implements DataSource {
    private static final int DEFAULT_POOL_SIZE = 3;
    @SuppressWarnings("unused")
    private final Logger logger = LoggerFactory.getLogger(SimpleDataSource.class);
    private final int maxPoolSize;
    private BlockingQueue<SimpleConnectionProxy> pool;
    private Collection<SimpleConnectionProxy> roster;
    private String driverClassName;
    private String username;
    private String password;
    private String url;
    private PrintWriter logWriter;

    /**
     * 构造一个基础数据源对象
     */
    public SimpleDataSource() {
        this.driverClassName = "";
        this.username = "";
        this.password = "";
        this.url = "";

        this.pool = null;
        this.maxPoolSize = DEFAULT_POOL_SIZE;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void initialize()
            throws Exception {
        super.initialize();

        initDriver();

        final Collection<SimpleConnectionProxy> tmp = new ArrayList<>(this.maxPoolSize);
        for (int i = 0; i < maxPoolSize; ++i) {
            tmp.add(new SimpleConnectionProxy(this));
        }

        this.roster = new ArrayList<>(tmp);
        this.pool = new ArrayBlockingQueue<>(this.maxPoolSize, false, tmp);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void dispose()
            throws Exception {
        for (final SimpleConnectionProxy proxy : this.roster) {
            if (!proxy.isFree()) {
                logger.warn("{} is not free!", proxy);
            }
            proxy.shutdown();
        }
    }

    /**
     * 获取数据库驱动的类型名称。
     *
     * @return the driverClassName 数据库驱动的类型名称。
     */
    public String getDriverClassName() {
        return driverClassName;
    }

    /**
     * 设置数据库驱动的类型名称
     *
     * @param driverClassName
     *         数据库驱动的类型名称。
     * @throws NullPointerException
     *         如果参数{@code driverClassName}是{@code null}。
     * @throws IllegalArgumentException
     *         如果参数{@code driverClassName}只包含空白字符。
     */
    public void setDriverClassName(
            String driverClassName
    ) {
        this.driverClassName = notBlank(driverClassName, "driverClassName").trim();
    }

    /**
     * 设置访问数据库的用户名。
     *
     * @return 访问数据库的用户名。
     */
    public String getUsername() {
        return this.username;
    }

    /**
     * 设置访问数据库的用户名。
     *
     * @param username
     *         访问数据库的用户名，自动去掉首尾空格。
     */
    public void setUsername(
            String username
    ) {
        this.username = trimToEmpty(username);
    }

    /**
     * 获取访问数据库的口令。
     *
     * @return 访问数据库的口令。
     */
    public String getPassword() {
        return this.password;
    }

    /**
     * 设置访问数据库的口令。
     *
     * @param password
     *         访问数据库的口令，自动去掉首尾空格。
     */
    public void setPassword(
            String password
    ) {
        this.password = trimToEmpty(password);
    }

    /**
     * 获取访问数据库的URL。
     *
     * @return 访问数据库的URL。
     */
    public String getUrl() {
        return this.url;
    }

    /**
     * 设置访问数据库的URL。
     *
     * @param url
     *         访问数据库的URL。
     * @throws IllegalArgumentException
     *         如果参数{@code url}是{@code null}或者只包含空白字符。
     */
    public void setUrl(
            String url
    ) {
        this.url = notBlank(url, "url");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PrintWriter getLogWriter()
            throws SQLException {
        return logWriter;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setLogWriter(
            PrintWriter logWriter
    )
            throws SQLException {
        this.logWriter = logWriter;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T unwrap(
            Class<T> clazz
    )
            throws SQLException {
        throw new SQLException(SimpleDataSource.class.getCanonicalName() + " is not a wrapper.");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isWrapperFor(
            Class<?> clazz
    )
            throws SQLException {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Connection getConnection()
            throws SQLException {
        try {
            final SimpleConnectionProxy proxy = this.pool.take();
            if (!proxy.isFree()) {
                throw new IllegalStateException(proxy + " is not free");
            }
            proxy.test();
            proxy.setFree(false);
            return proxy;
        } catch (InterruptedException ex) {
            throw new SQLException("cannot cate connection", ex);
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Connection getConnection(
            String username,
            String password
    )
            throws SQLException {
        throw new UnsupportedOperationException("use getConnection() instead");
    }

    /**
     * 将关闭的连接代理加入到队列中。
     *
     * @param proxy
     *         已关闭的连接代理。
     * @throws IllegalArgumentException
     *         如果参数{@code proxy}是{@code null}。
     */
    void restoreConnection(
            SimpleConnectionProxy proxy
    ) {
        notNull(proxy, "proxy");
        proxy.setFree(true);
        this.pool.offer(proxy);
    }

    /**
     * 初始化数据库驱动。
     *
     * @throws ClassNotFoundException
     *         如果无法加载指定的数据库驱动类型。
     */
    private void initDriver()
            throws ClassNotFoundException {
        Class.forName(this.driverClassName);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return this.getDriverClassName() + "{url=" + this.getUrl() + ",username=" + this.getUsername() + "}";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getLoginTimeout()
            throws SQLException {
        throw new UnsupportedOperationException("not supported by BasicDataSource");
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public java.util.logging.Logger getParentLogger()
            throws SQLFeatureNotSupportedException {
        throw new SQLFeatureNotSupportedException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setLoginTimeout(
            int timeout
    )
            throws SQLException {
        throw new UnsupportedOperationException("not supported by BasicDataSource");
    }
}
